﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Microsoft.CSharp;
using Framework.Extensions;
using System.CodeDom.Compiler;
using System.Linq.Expressions;

namespace Framework.Reflection
{
    public static class Compiler
    {
        static string template = @"
using System;
using System.Linq.Expressions;

namespace DynamicNamespace
{
public class DynaClass{rand}
{
{output} _LambdaMethod = {expression} 
public {output} DynaLambda 
{
get{ return _LambdaMethod; }
} 

}
}";


        static string GetProperFullName(this Type t)
        {
            if (t.IsGenericType)
            {
                var result = t.GetGenericTypeDefinition();
                var cleanedName = result.FullName.Substring(0, result.FullName.Length - 2); // => remove the '1 symbol that .net automatically adds to generic names 
                var args = t.GetGenericArguments();
                cleanedName += "<" + string.Join(",", args.Select(x => x.GetProperFullName())) + ">";
                return cleanedName;
            }
            else return t.FullName ?? t.Name;
        }

        static Dictionary<int, object> OtherCache = new Dictionary<int, object>();

        public static T GetLambda<T>(string expression) where T : class
        {

            var t = typeof(T);
            var hash = (expression + t.Name).GetHashCode();
            if (OtherCache.ContainsKey(hash)) return (T)OtherCache[hash];

            if (t.IsGenericType && (t.Name.StartsWith("Func") || t.Name.StartsWith("Action")))
            {
                T result = (T)Convert.ChangeType(GetDelegate(expression, t.GetProperFullName()), typeof(T));
                OtherCache.Add(hash, result);
                return result;
            }
            else throw new Exception("LambdaCompiler only builds Func<> and Action<> expressions");
        }


        static Delegate GetDelegate(string expression, string outputType)
        {
            Delegate output = null;

            var rand = Guid.NewGuid().ToString().Replace("-", "_");

            var assembled = template
                .Replace("{expression}", expression)
                .Replace("{output}", outputType)
                .Replace("{rand}", rand);

            var a = GetDynamicAssembly(assembled);
            object o = a.CreateInstance("DynamicNamespace.DynaClass" + rand);
            output = (Delegate)o.GetType().GetProperty("DynaLambda").GetValue(o, null);
            return output;
        }


        public static Assembly GetDynamicAssembly(string sourceCode)
        {
            Assembly a = null;

            using (CSharpCodeProvider codeProvider = new CSharpCodeProvider())
            {
                List<string> assemblies = new List<string>();
                foreach (Assembly x in AppDomain.CurrentDomain.GetAssemblies())
                {
                    try
                    {
                        assemblies.Add(x.Location);
                    }
                    catch (NotSupportedException)
                    {
                        // Dynamic assemblies will throw, and in .net 3.5 there seems to be no way of finding out whether the assembly is dynamic before trying.
                    }
                }

                //AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

                var compilerParams = new CompilerParameters(assemblies.Where(x => !string.IsNullOrWhiteSpace(x)).ToArray()) { GenerateExecutable = false, GenerateInMemory = false  };
                //compilerParams.ReferencedAssemblies.Add(typeof(Dy
                CompilerResults r = codeProvider.CompileAssemblyFromSource(compilerParams, sourceCode);

                if (r.Errors.HasErrors)
                    throw new Exception("Errors compiling expression: " + string.Join(Environment.NewLine, r.Errors.OfType<CompilerError>().Select(e => e.ErrorText).ToArray()));

                //AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(CurrentDomain_AssemblyResolve);

                a = r.CompiledAssembly;
            }

            return a;
        }

        static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            var t = args.Name;

            return null;
        }
    }
}
